﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;

namespace DaveSexton.Labs
{
	internal sealed class LabFactory : LabFactoryBase<Assembly>
	{
		#region Public Properties
		#endregion

		#region Private / Protected
		private ICollection<Type> CatalogLabTypes
		{
			get
			{
				Contract.Ensures(Contract.Result<ICollection<Type>>() != null);

				if (catalogLabTypes == null)
				{
					catalogLabTypes = Catalog.LabTypes.ToList().AsReadOnly();
				}

				return catalogLabTypes;
			}
		}

		private ICollection<Type> catalogLabTypes;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="LabFactory" /> class.
		/// </summary>
		internal LabFactory(LabCatalog catalog)
			: base(catalog)
		{
			Contract.Requires(catalog != null);
		}
		#endregion

		#region Methods
		protected override IEnumerable<ILab> ImportLabs(Assembly discoveryCatalog, IList<Type> excludeLabs)
		{
			return from type in discoveryCatalog.GetExportedTypes()
						 where typeof(ILab).IsAssignableFrom(type)
								&& !type.IsAbstract
								&& type.GetCustomAttributes(typeof(PartNotDiscoverableAttribute), inherit: false).Length == 0
								&& type.GetConstructors(BindingFlags.Public | BindingFlags.Instance).Where(c => c.GetParameters().Length == 0).Count() > 0
								&& ShouldImportLab(type, excludeLabs)
						 select (ILab) Activator.CreateInstance(type);
		}

		private bool ShouldImportLab(Type labType, IList<Type> excludeLabs)
		{
			Contract.Requires(labType != null);
			Contract.Requires(excludeLabs != null);

			if (excludeLabs.Contains(labType))
			{
				return false;
			}
			else if ((Catalog.Activation & LabActivationStrategies.DiscoverExplicitLabTypesOnly) == LabActivationStrategies.DiscoverExplicitLabTypesOnly)
			{
				return CatalogLabTypes.Contains(labType);
			}
			else if ((Catalog.Activation & LabActivationStrategies.ExcludeCatalogLabTypesFromDiscovery) == LabActivationStrategies.ExcludeCatalogLabTypesFromDiscovery)
			{
				return !CatalogLabTypes.Contains(labType);
			}

			return true;
		}
		#endregion
	}
}